import os
import time
import subprocess
from datetime import datetime
import logging
from logging.handlers import RotatingFileHandler
import requests
from flask import Flask, request, jsonify
import schedule
import threading

app = Flask(__name__)
app.json.ensure_ascii = False

# 从环境变量获取配置
BACKUP_DIR = os.getenv('BACKUP_DIR', '/opt/mydockertools/gohttpserver/data/mysql_baks/')
BACKUP_EXPIRE_TIME = os.getenv('BACKUP_SAVE_TIME', '60')
BACKUP_PROJECT = os.getenv('BACKUP_PROJECT', 'test')  # 发送消息至企微时候使用
MYSQL_HOST = os.getenv('MYSQL_HOST', '127.0.0.1')
MYSQL_USER = os.getenv('MYSQL_USER', 'root')
MYSQL_PASSWORD = os.getenv('MYSQL_PASSWORD', '123456') # 建议使用secret引入
MYSQL_PORT = os.getenv('MYSQL_PORT', '3306')
BUSINESS_DB_FRONT = os.getenv('BUSINESS_DB_FRONT', 'n9e') # 通常某个项目的库都会有自己的前缀
WECHAT_WEBHOOK_URL = os.getenv('WECHAT_WEBHOOK_URL', 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=2a07a707-af1b-4223-af6e-b2fbbfb8c3c1')
BACK_TIME =  os.getenv('BACK_TIME', '02:20')
BEIP_TOOL = os.getenv('BEIP_TOOL', False)

# 配置日志
log_file = f"{BACKUP_DIR}/app.log"
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s - %(levelname)s - %(message)s",
    handlers=[
        RotatingFileHandler(log_file, maxBytes=1024*1024*5, backupCount=3),
        logging.StreamHandler()
    ]
)

def send_wechat_message(message, is_success):
    """
    发送企业微信消息
    """
    if not WECHAT_WEBHOOK_URL:
        logging.warning("企业微信Webhook URL未配置，无法发送消息")
        return "企业微信Webhook URL未配置，无法发送消息"

    title = f"<font color={'info' if is_success else 'warning'}>{'数据库备份成功' if is_success else '数据库备份失败'}</font>"
    markdown = f"""
**备份状态**: {title}
**备份项目**: {BACKUP_PROJECT}
**备份时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
**备份目录**: {BACKUP_DIR}
**消息内容**: {message} 
"""
    payload = {
        "msgtype": "markdown",
        "markdown": {
            "content": markdown
        }
    }
    try:
        response = requests.post(WECHAT_WEBHOOK_URL, json=payload, timeout=10)
        response.raise_for_status()
        logging.info("企业微信消息发送成功")
        return markdown
    except Exception as e:
        logging.error(f"企业微信消息发送失败: {str(e)}")
        return f"企业微信消息发送失败: {str(e)}"

def execute_command(command):
    """
    执行命令并记录日志
    """
    try:
        result = subprocess.run(
            command,
            shell=True,
            check=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True
        )
        logging.info(f"命令执行成功: {command}")
        return True, result.stdout
    except subprocess.CalledProcessError as e:
        error_msg = f"命令执行失败: {command}\n错误信息: {e.stderr}"
        return False, error_msg
def create_bak_dir():
    # 创建备份目录结构
    today_dir = os.path.join(BACKUP_DIR, datetime.now().strftime('%Y%m%d'))
    current_time = datetime.now().strftime("%Y%m%d%H%M")
    os.makedirs(today_dir, exist_ok=True)
    return today_dir, current_time
def backup_database(db_name, today_dir, current_time):
    temp_sql_file = os.path.join(today_dir, f"{BACKUP_PROJECT}-{db_name}-{current_time}.sql")
    gzip_file = f"{temp_sql_file}.gz"
    # 1. 执行 mysqldump
    backup_cmd = (
        f"mysqldump -h {MYSQL_HOST} -P {MYSQL_PORT} -u {MYSQL_USER} -p{MYSQL_PASSWORD} "
        f"{db_name} --single-transaction --default-character-set=utf8mb4 > {temp_sql_file}"
    )
    success, msg = execute_command(backup_cmd)
    if not success:
        logging.error(f"备份数据库 {db_name} 失败: {msg}")
        return False, msg

    # 2. 检查临时文件是否存在
    if not os.path.exists(temp_sql_file):
        logging.error(f"备份文件不存在: {temp_sql_file}")
        return False, f"备份文件不存在: {temp_sql_file}"

    # 3. 执行 gzip 压缩
    gzip_cmd = f"gzip {temp_sql_file}"
    success, msg = execute_command(gzip_cmd)
    if not success:
        logging.error(f"压缩数据库 {db_name} 失败: {msg}")
        if os.path.exists(gzip_file):
            os.remove(gzip_file)
            logging.warning(f"已删除失败的压缩文件: {gzip_file}")
        return False, msg

    # 4. 检查压缩后的文件是否存在
    if not os.path.exists(gzip_file):
        logging.error(f"压缩文件不存在: {gzip_file}")
        return False, f"压缩文件不存在: {gzip_file}"
    return True, None

def all_mysql_backup():
    # **全库备份**
    today_dir, current_time = create_bak_dir()
    backup_success, _ = backup_database("-A", today_dir, current_time)
    return backup_success, ['all_database'] if not backup_success else []
def business_mysql_backup():
    today_dir, current_time = create_bak_dir()
    # 初始化失败数据库列表
    failed_dbs = []
    logging.info('开始单库备份业务库')
    # 获取业务数据库列表
    db_list_cmd = f"mysql -h {MYSQL_HOST} -u {MYSQL_USER} -p{MYSQL_PASSWORD} -P {MYSQL_PORT} -e 'show databases' | grep '{BUSINESS_DB_FRONT}'"
    success, output = execute_command(db_list_cmd)

    if not success:
        msg = send_wechat_message("获取业务数据库列表失败", False)
        return msg

    business_dbs = [db.strip() for db in output.strip().split('\n') if db.strip()]
    # 确认是否需要备份 beip_tool 数据库
    if BEIP_TOOL:
        business_dbs.append('beip_tool')
    # 备份业务数据库
    backup_success = True
    for db in business_dbs:
        success, msg = backup_database(db, today_dir, current_time)
        if not success:
            backup_success = False
            failed_dbs.append(db)
            logging.error(f"备份数据库 {db} 失败: {msg}")
    return backup_success, failed_dbs

def backup_job():
    alldb_backup_success, alldb_failed_dbs = all_mysql_backup()
    business_backup_success, business_failed_dbs = business_mysql_backup()
    backup_success = alldb_backup_success and business_backup_success
    failed_dbs = alldb_failed_dbs + business_failed_dbs
    if backup_success:
        find_cmd = f"find {BACKUP_DIR} -mtime +{BACKUP_EXPIRE_TIME} -delete"
        _, msg = execute_command(find_cmd)
        message = f"定时备份所有库备份完成，{BACKUP_EXPIRE_TIME}天前的备份已清理"
    else:
        message = f"定时备份部分库备份失败：{', '.join(failed_dbs)}"
    send_wechat_message(message, backup_success)
def start_scheduler():
    schedule.every().day.at(BACK_TIME).do(backup_job)  # 每天凌晨2点执行
    while True:
        schedule.run_pending()
        time.sleep(1)

@app.route('/', methods=['POST', 'GET'])
def index():
    return """
    <title>MySQL Dump Tool</title>
    <h1>Mysql database backup</h1>
    <p>Welcome to the Mysql database backup API.</p>
    <h2>Usage</h2>
    <ul>
        <li><b>Switch to Maintenance Mode:</b> <code>POST /backup</code></li>
    </ul>
    """

@app.route('/backup', methods=['POST'])
def trigger_backup():
    backup_success, failed_dbs = business_mysql_backup()
    if backup_success:
        message = f"调用接口备份所有库已完成"
    else:
        message = f"调用接口备份部分库失败：{', '.join(failed_dbs)}"
    msg = send_wechat_message(message, backup_success)
    return jsonify({"message": msg})

if __name__ == "__main__":
    scheduler_thread = threading.Thread(target=start_scheduler)
    scheduler_thread.daemon = True  # 设置为守护线程，确保主线程结束时线程也结束
    scheduler_thread.start()
    app.run(host='0.0.0.0', port=5999)

